﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;

namespace Ndo
{
    using Ndo.Algorithms;
    using Ndo.Reflections;
    class ConvertorGlobal
    {
        static ConvertorGlobal()
        {
            DateTime dt1 = DateTime.Now;
            AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
            foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
            {
                AddAssembly(asm);
            }
            System.Diagnostics.Trace.WriteLine(DateTime.Now-dt1);
        }

        internal static readonly Dictionary<Type, object> AllTypes = new Dictionary<Type, object>();
        internal static readonly Dictionary<Type, Dictionary<Type, Invoker>> AllImplicits = new Dictionary<Type, Dictionary<Type, Invoker>>();
        internal static readonly Dictionary<Pair<Type>, List<Invoker>> ImplicitDict = new Dictionary<Pair<Type>, List<Invoker>>();
        private static void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
        {
            AddAssembly(args.LoadedAssembly);

            //reset invalid convert
            lock(ImplicitDict)
            {
                ImplicitDict.Clear();
            }
        }
        private static bool AddAssembly(Assembly asm)
        {
            Type[] ts;
            try
            {
                ts = asm.GetTypes();
            }
            catch
            {
                return false;
            }
            lock(AllTypes)
            {
                foreach (Type type in ts)
                {
                    if(!AllTypes.ContainsKey(type))
                    {
                        AllTypes.Add(type, null);
                        foreach (MethodInfo mi in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
                        {
                            if (mi.Name == "op_Implicit" && mi.IsSpecialName)
                            {
                                AddImplicitMethod(mi);
                            }
                        }
                    }
                }
            }
            return true;
        }

        private static bool AddImplicitMethod(MethodInfo mi)
        {
            ParameterInfo[] ps = mi.GetParameters();
            if (ps.Length == 1)
            {
                Type t = ps[0].ParameterType;
                Dictionary<Type, Invoker> dict;
                lock (AllImplicits)
                {
                    if (!AllImplicits.TryGetValue(t, out dict))
                    {
                        dict = new Dictionary<Type, Invoker>();
                        AllImplicits.Add(t, dict);
                    }
                }
                t = mi.ReturnType;
                lock(dict)
                {
                    if (!dict.ContainsKey(t) || mi.DeclaringType != t)
                    {
                        dict[t] = new Invoker(mi);
                    }
                }
                return true;
            }
            return false;
        }

        public static bool AddType(Type type)
        {
            lock (AllTypes)
            {
                if (AllTypes.ContainsKey(type))
                {
                    return false;
                }
                AllTypes.Add(type, null);
                foreach (MethodInfo mi in type.GetMethods(BindingFlags.Public | BindingFlags.Static))
                {
                    if (mi.Name == "op_Implicit" && mi.IsSpecialName)
                    {
                        AddImplicitMethod(mi);
                    }
                }
            }
            return true;
        }
    }

    public class Convertor
    {
        public Convertor()
            :this(false)
        { }
        public Convertor(bool excludeImplicit)
        {
            this.excludeImplicit = excludeImplicit;
        }

        private readonly bool excludeImplicit;
        public bool ExcludeImplicit
        {
            get
            {
                return excludeImplicit;
            }
        }

        private Dictionary<Type, Dictionary<Type, Invoker>> converts = new Dictionary<Type, Dictionary<Type, Invoker>>();
        private readonly Dictionary<Pair<Type>, List<Invoker>> listDict = new Dictionary<Pair<Type>, List<Invoker>>();

        public bool AddMethod(Invoker invoker)
        {
            MethodInfo mi = invoker.Method;
            ParameterInfo[] ps = mi.GetParameters();
            if (ps.Length == 1 && mi.ReflectedType != typeof(void))
            {
                Type t = ps[0].ParameterType;
                Dictionary<Type, Invoker> dict;
                lock (converts)
                {
                    if (!converts.TryGetValue(t, out dict))
                    {
                        dict = new Dictionary<Type, Invoker>();
                        converts.Add(t, dict);
                    }
                }
                t = mi.ReturnType;
                lock (dict)
                {
                    if (!dict.ContainsKey(t) || mi.DeclaringType != t)
                    {
                        dict[t] = invoker;
                    }
                }
                lock (listDict)
                {
                    listDict.Clear();
                }
                return true;
            }
            return false;
        }
        public Invoker GetMethod(Type srcType, Type destType)
        {
            Invoker invoker = null;
            Dictionary<Type, Invoker> dict;
            bool b = false;
            lock (converts)
            {
                if (!converts.TryGetValue(srcType, out dict))
                {
                    b = true;
                }
            }
            if (!b)
            {
                lock (dict)
                {
                    if (!dict.TryGetValue(destType, out invoker))
                    {
                        b = true;
                    }
                }
            }
            if (b && !excludeImplicit)
                return GetImplicitMethod(srcType, destType);
            return invoker;
        }
        private IEnumerable<Type> Enumerate(Type srcType)
        {
            Dictionary<Type, Invoker> dict;
            if (converts.TryGetValue(srcType, out dict))
            {
                foreach (KeyValuePair<Type, Invoker> pair in dict)
                {
                    yield return pair.Key;
                }
            }
            if (ConvertorGlobal.AllImplicits.TryGetValue(srcType, out dict))
            {
                foreach (KeyValuePair<Type, Invoker> pair in dict)
                {
                    yield return pair.Key;
                }
            }
        }

        private IEnumerable<Type> Enumerate2(Type srcType)
        {
            Dictionary<Type, Invoker> dict;
            if (converts.TryGetValue(srcType, out dict))
            {
                foreach (KeyValuePair<Type, Invoker> pair in dict)
                {
                    yield return pair.Key;
                }
            }
        }

        private List<Invoker> getMethodList(Type srcType, Type destType)
        {
            List<Invoker> list;
            lock (listDict)
            {
                if (listDict.TryGetValue(new Pair<Type>(srcType, destType), out list))
                {
                    return list;
                }
            }
            Type[] ts;
            if (!excludeImplicit)
            {
                lock (ConvertorGlobal.AllImplicits)
                {
                    ts = (new BFS<Type>(Enumerate)).GetShortestPath(srcType, destType);
                }
            }
            else
            {
                ts = (new BFS<Type>(Enumerate2)).GetShortestPath(srcType, destType);
            }
            if (ts != null && ts.Length != 0)
            {
                list = new List<Invoker>();
                Type t = ts[0];
                for (int i = 1; i < ts.Length; i++)
                {
                    Invoker mi = GetMethod(t, ts[i]);
                    if (mi == null)
                    {
                        list = null;
                        break;
                    }
                    t = ts[i];
                    list.Add(mi);
                }
            }
            lock (listDict)
            {
                listDict[new Pair<Type>(srcType, destType)] = list;
            }
            return list;
        }
        #region Static
        public static Invoker GetImplicitMethod(Type srcType, Type destType)
        {
            ConvertorGlobal.AddType(srcType);
            ConvertorGlobal.AddType(destType);
            Invoker invoker;
            Dictionary<Type, Invoker> dict;
            lock (ConvertorGlobal.AllImplicits)
            {
                if (!ConvertorGlobal.AllImplicits.TryGetValue(srcType, out dict))
                {
                    return null;
                }
            }
            lock (dict)
            {
                if (!dict.TryGetValue(destType, out invoker))
                    return null;
            }
            return invoker;
        }

        private static IEnumerable<Type> EnumerateImplicit(Type srcType)
        {
            Dictionary<Type, Invoker> dict;
            if (ConvertorGlobal.AllImplicits.TryGetValue(srcType, out dict))
            {
                foreach (KeyValuePair<Type, Invoker> pair in dict)
                {
                    yield return pair.Key;
                }
            }
        }
        private static List<Invoker> getImplicitMethodList(Type srcType, Type destType)
        {
            List<Invoker> list;
            lock (ConvertorGlobal.ImplicitDict)
            {
                if (ConvertorGlobal.ImplicitDict.TryGetValue(new Pair<Type>(srcType, destType), out list))
                {
                    return list;
                }
            }
            Type[] ts;
            lock (ConvertorGlobal.AllImplicits)
            {
                ts = new BFS<Type>(EnumerateImplicit).GetShortestPath(srcType, destType);
            }
            if (ts != null && ts.Length != 0)
            {
                list = new List<Invoker>();
                Type t = ts[0];
                for (int i = 1; i < ts.Length; i++)
                {
                    Invoker invoker = GetImplicitMethod(t, ts[i]);
                    if (invoker == null)
                    {
                        list = null;
                        break;
                    }
                    t = ts[i];
                    list.Add(invoker);
                }
            }
            lock (ConvertorGlobal.ImplicitDict)
            {
                ConvertorGlobal.ImplicitDict[new Pair<Type>(srcType, destType)] = list;
            }
            return list;
        }
        #endregion

        private static object InvokeList(object src, List<Invoker> list)
        {
            foreach (Invoker invoker in list)
            {
                src = invoker.Invoke(src);
            }
            return src;
        }


        public object DirectConvert(object src, Type destType)
        {
            if (src == null)
                return null;
            Type srcType = src.GetType();
            if (destType.IsAssignableFrom(srcType))
                return src;
            Invoker invoker;
            invoker = GetMethod(srcType, destType);
            if (invoker != null)
                return invoker.Invoke(src);
            throw new InvalidProgramException();
        }
        public bool CanDirectConvert(Type srcType, Type destType)
        {
            return GetMethod(srcType, destType) != null;
        }
        public object Convert(object src, Type destType)
        {
            if (src == null)
                return null;
            Type srcType = src.GetType();
            Invoker invoker = GetMethod(srcType, destType);
            if (invoker != null)
                return invoker.Invoke(src); ;
            List<Invoker> list = getMethodList(srcType, destType);
            if (list != null)
                return InvokeList(src, list);
            throw new InvalidProgramException();
        }
        public bool CanConvert(Type srcType, Type destType)
        {
            return getMethodList(srcType, destType) != null;
        }

        public static object DirectImplicit(object src, Type destType)
        {
            if (src == null)
                return null;
            Type srcType = src.GetType();
            if (destType.IsAssignableFrom(srcType))
                return src;
            Invoker invoker = GetImplicitMethod(srcType, destType);
            if (invoker != null)
                return invoker.Invoke(src);
            throw new InvalidProgramException();
        }
        public static bool CanDirectImplicit(Type srcType, Type destType)
        {
            return GetImplicitMethod(srcType, destType) != null;
        }
        public static object Implicit(object src, Type destType)
        {
            if (src == null)
                return null;
            Type srcType = src.GetType();
            Invoker invoker = GetImplicitMethod(srcType, destType);
            if (invoker != null)
                return invoker.Invoke(src); ;
            List<Invoker> list = getImplicitMethodList(srcType, destType);
            if (list != null)
                return InvokeList(src, list);
            throw new InvalidProgramException();
        }
        public static bool CanImplicit(Type srcType, Type destType)
        {
            return getImplicitMethodList(srcType, destType) != null;
        }
        public static TDest DirectImplicit<TDest>(object src)
        {
            return (TDest)DirectImplicit(src, typeof(TDest));
        }
        public static TDest Implicit<TDest>(object src)
        {
            return (TDest)Implicit(src, typeof(TDest));
        }
        public TDest DirectConvert<TDest>(object src)
        {
            return (TDest)DirectConvert(src, typeof(TDest));
        }
        public TDest Convert<TDest>(object src)
        {
            return (TDest)Convert(src, typeof(TDest));
        }

        public static Invoker[] GetImplicitMethodList(Type srcType, Type destType)
        {
            List<Invoker> list = getImplicitMethodList(srcType, destType);
            if (list == null)
                return null;
            return list.ToArray();
        }
        public Invoker[] GetMethodList(Type srcType, Type destType)
        {
            List<Invoker> list = getMethodList(srcType, destType);
            if (list == null)
                return null;
            return list.ToArray();
        }

    }
}
